﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TextTemplating;
using Lilium.Collections;
using System.IO;
using System.Globalization;
using System.CodeDom;
using System.Runtime.Remoting.Messaging;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Drawing.Design;

namespace Lilium.T4.Hosting
{
    public class PropertyProcessor : DirectiveProcessor
    {
        public const string PropertyDirectiveName = "property";

        public override void FinishProcessingRun()
        {
            return; 
        }

        public override string GetClassCodeForProcessingRun()
        {
            using (var writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                foreach (var propertyDescriptor in this.PropertyDescriptors)
                {
                    //Create field

                    var field = new CodeMemberField(propertyDescriptor.Type, propertyDescriptor.FieldName)
                    {
                        Attributes = MemberAttributes.Private
                    };

                    LanguageProvider.GenerateCodeFromMember(field, writer, CodeGeneratorOptions);

                    //Create the property for each argument
                    var property = new CodeMemberProperty
                    {
                        Name = propertyDescriptor.Name,
                        Type = new CodeTypeReference(propertyDescriptor.Type),
                        Attributes = MemberAttributes.Public,
                        HasGet = true,
                        HasSet = false
                    };
                    property.GetStatements.Add(
                        new CodeMethodReturnStatement(new CodeFieldReferenceExpression(
                                                          new CodeThisReferenceExpression(),
                                                          propertyDescriptor.FieldName)));
                    //Add editor attribute
                    if (!string.IsNullOrEmpty(propertyDescriptor.EditorType))
                    {
                        property.CustomAttributes.Add(new CodeAttributeDeclaration(
                                                          new CodeTypeReference(typeof(EditorAttribute)),
                                                          new CodeAttributeArgument(
                                                              new CodeTypeOfExpression(propertyDescriptor.EditorType)),
                                                          new CodeAttributeArgument(
                                                              new CodeTypeOfExpression(typeof(UITypeEditor)))));
                    }

                    //Add convertor attribute
                    if (!string.IsNullOrEmpty(propertyDescriptor.ConverterType))
                    {
                        property.CustomAttributes.Add(new CodeAttributeDeclaration(
                                                          new CodeTypeReference(typeof(TypeConverter)),
                                                          new CodeAttributeArgument(
                                                              new CodeTypeOfExpression(propertyDescriptor.ConverterType))));
                    }

                    LanguageProvider.GenerateCodeFromMember(property, writer, CodeGeneratorOptions);
                }

                var res = writer.ToString();
                return res;
            }
        }

        public override string[] GetImportsForProcessingRun()
        {
            return null;
        }

        public override string GetPostInitializationCodeForProcessingRun()
        {
            using (var writer = new StringWriter(CultureInfo.InvariantCulture))
            {
                foreach (PropertyDescriptor propertyDescriptor in this.PropertyDescriptors)
                {
                    //Generate initialization code for each argument
                    //_FieldName = (FieldType) this.GetHostOption("_FieldName");
                    var assignment = new CodeAssignStatement(
                        new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), propertyDescriptor.FieldName),
                        new CodeCastExpression(
                            new CodeTypeReference(propertyDescriptor.Type),
                            new CodeMethodInvokeExpression(
                                new CodeTypeReferenceExpression(typeof(CallContext)),
                                "LogicalGetData",
                                new CodePrimitiveExpression(propertyDescriptor.Name))));

                    LanguageProvider.GenerateCodeFromStatement(assignment, writer, CodeGeneratorOptions);
                }

                var res = writer.ToString();
                return res;
            }
        }

        public override string GetPreInitializationCodeForProcessingRun()
        {
            return null;
        }

        public override string[] GetReferencesForProcessingRun()
        {
            //if (_editorUsed)
            //{
            //    return new[] { typeof(UITypeEditor).Assembly.Location };
            //}
            return null;
        }

        public override bool IsDirectiveSupported(string directiveName)
        {
            return PropertyDirectiveName.Equals(directiveName, StringComparison.OrdinalIgnoreCase);
        }

        public override void ProcessDirective(string directiveName, IDictionary<string, string> arguments)
        {
            if (PropertyDirectiveName.Equals(directiveName, StringComparison.OrdinalIgnoreCase))
            {
                var propertyDescriptor = new PropertyDescriptor(
                    arguments.TryGetValue("name"),
                    arguments.TryGetValue("type", "System.String"),
                    arguments.TryGetValue("converter"),
                    arguments.TryGetValue("editor"));
                PropertyDescriptors.Add(propertyDescriptor);
            }
        }

        public override void StartProcessingRun(CodeDomProvider languageProvider, string templateContents, CompilerErrorCollection errors)
        {
            if (languageProvider == null) throw new ArgumentNullException("languageProvider");

            base.StartProcessingRun(languageProvider, templateContents, errors);
            LanguageProvider = languageProvider;
        }

        #region private CodeDomProvider LanguageProvider

        private CodeDomProvider m_LanguageProvider;

        private CodeDomProvider LanguageProvider
        {
            get
            {
                return m_LanguageProvider;
            }
            set
            {
                m_LanguageProvider = value;
            }
        }

        #endregion

        #region public CodeGeneratorOptions CodeGeneratorOptions

        private readonly CodeGeneratorOptions m_CodeGeneratorOptions = new CodeGeneratorOptions
        {
            BlankLinesBetweenMembers = true,
            IndentString = "        ",
            VerbatimOrder = true,
            BracingStyle = "C"
        };

        public CodeGeneratorOptions CodeGeneratorOptions
        {
            get
            {
                return m_CodeGeneratorOptions;
            }
        }

        #endregion

        #region private ICollection<PropertyDescriptor> PropertyDescriptors

        private readonly ICollection<PropertyDescriptor> m_PropertyDescriptors = new List<PropertyDescriptor>();

        private ICollection<PropertyDescriptor> PropertyDescriptors
        {
            get
            {
                return m_PropertyDescriptors;
            }
        }

        #endregion

        #region private sealed class PropertyDescriptor

        private sealed class PropertyDescriptor
        {
            public PropertyDescriptor(string name, string type, string editorType, string converterType)
            {
                m_ConverterType = converterType;
                m_EditorType = editorType;
                m_Name = name;
                m_Type = type;
            }

            #region public string ConverterType

            private readonly string m_ConverterType;

            public string ConverterType
            {
                get
                {
                    return m_ConverterType;
                }
            }

            #endregion

            #region public string EditorType

            private readonly string m_EditorType;

            public string EditorType
            {
                get
                {
                    return m_EditorType;
                }
            }

            #endregion

            #region public string FieldName

            public string FieldName
            {
                get
                {
                    return "m_" + Name;
                }
            }

            #endregion

            #region public string Name

            private readonly string m_Name;

            public string Name
            {
                get
                {
                    return m_Name;
                }
            }

            #endregion

            #region public string Type

            private readonly string m_Type;

            public string Type
            {
                get
                {
                    return m_Type;
                }
            }

            #endregion
        }

        #endregion
    }
}
